/*
 * Copyright 2015 泛泛o0之辈
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.jfast.framework.schedule.analyzer;

import cn.jfast.framework.base.util.Assert;
import cn.jfast.framework.base.util.ObjectUtils;
import cn.jfast.framework.schedule.ScheduleCronException;

import java.util.*;

/**
 * cron 时间分析
 */
public class TimeWrapper {
    private final String time;
    private final CronTime cronType;
    private final TimeType timeType;
    private int ERROR_TIME = -1;
    private CronAnalyzer analizer;
    private int returnTime = ERROR_TIME;
    boolean isFirst = true;
    Integer[] timeZones = null;
    private Calendar currCalendar = Calendar.getInstance();

    TimeWrapper(String time, CronTime cronType, TimeType timeType, CronAnalyzer analizer) {
        this.time = time;
        this.cronType = cronType;
        this.timeType = timeType;
        this.analizer = analizer;
        currCalendar.setTime(new Date());
        List<String> timeList = new ArrayList<String>();
        if (cronType == CronTime.HYPHEN_AND_COMMA) {
            String[] times = time.split(",");
            for (String itime : times) {
                Assert.notEmpty(itime, "CRON表达式解析错误: [ " + analizer.getCronExpression() + " ]");
                String[] items = itime.split("-");
                if (items.length == 1) {
                    timeList.add(items[0]);
                    continue;
                }
                if (items[0].equals("")
                        || items[1].equals(""))
                    throw new ScheduleCronException("CRON表达式解析错误: [ " + analizer.getCronExpression() + " ]");
                if (Integer.parseInt(items[0]) < Integer.parseInt(items[1])) {
                    for (int i = Integer.parseInt(items[0]); i <= Integer.parseInt(items[1]); i++) {
                        timeList.add(i + "");
                    }
                } else {
                    for (int i = Integer.parseInt(items[0]); i >= Integer.parseInt(items[1]); i--) {
                        timeList.add(i + "");
                    }
                }
            }
        } else if (cronType == CronTime.COMMA) {
            timeList = Arrays.asList(time.split(","));
        } else if (cronType == CronTime.HYPHEN) {
            String[] times = time.split("-");
            if (times[0].equals("")
                    || times[1].equals(""))
                throw new ScheduleCronException("CRON表达式解析错误: [ " + analizer.getCronExpression() + " ]");
            if (Integer.parseInt(times[0]) < Integer.parseInt(times[1])) {
                for (int i = Integer.parseInt(times[0]); i <= Integer.parseInt(times[1]); i++) {
                    timeList.add(i + "");
                }
            } else {
                for (int i = Integer.parseInt(times[0]); i >= Integer.parseInt(times[1]); i--) {
                    timeList.add(i + "");
                }
            }
        }
        Object[] timeArrays = timeList.toArray();
        timeZones = new Integer[timeArrays.length];
        for (int i = 0; i < timeArrays.length; i++)
            timeZones[i] = Integer.parseInt(String.valueOf(timeArrays[i]));
        if (!ObjectUtils.isEmpty(timeZones)) {
            Arrays.sort(timeZones);
            verifyTime(timeZones[0]);
            verifyTime(timeZones[timeZones.length - 1]);
        }

    }

    public CronTime getCronType() {
        return cronType;
    }

    public String getTime() {
        return time;
    }

    public int getExecuteTime() {
        if (cronType == CronTime.NUMBER) {
            returnTime = Integer.parseInt(time);
        } else {
            if (!isFirst) {
                returnTime = returnTime + 1;
            } else {
                isFirst = false;
                if (timeType == TimeType.YEAR) {
                    returnTime = currCalendar.get(Calendar.YEAR);
                } else if (timeType == TimeType.MONTH) {
                    returnTime = currCalendar.get(Calendar.MONTH) + 1;
                } else if (timeType == TimeType.DAY) {
                    returnTime = currCalendar.get(Calendar.DAY_OF_MONTH);
                } else if (timeType == TimeType.HOUR) {
                    returnTime = currCalendar.get(Calendar.HOUR_OF_DAY);
                } else if (timeType == TimeType.MINUTE) {
                    returnTime = currCalendar.get(Calendar.MINUTE);
                } else if (timeType == TimeType.SECOND) {
                    returnTime = currCalendar.get(Calendar.SECOND);
                }
            }
            timeBoundConvert();
            if (cronType != CronTime.ASTERISK) {
                returnTime = binarySearchTimePoint(timeZones, returnTime);
            }
        }
        if (returnTime == Integer.MAX_VALUE
                || returnTime == Integer.MIN_VALUE)
            return ERROR_TIME;
        return returnTime;

    }

    public int getDefaultTime() {
        if (cronType == CronTime.NUMBER) {
            returnTime = Integer.parseInt(time);
        } else {
            if (cronType != CronTime.ASTERISK) {
                returnTime = binarySearchTimePoint(timeZones, -1);
            } else {
                if (timeType == TimeType.MONTH
                        || timeType == TimeType.DAY) {
                    returnTime = 1;
                } else if (timeType == TimeType.HOUR
                        || timeType == TimeType.MINUTE
                        || timeType == TimeType.SECOND) {
                    returnTime = 0;
                }
            }
        }
        return returnTime;
    }

    private void timeBoundConvert() {
        if (timeType == TimeType.MONTH) {
            returnTime = returnTime > 12 ? 1 : returnTime;
        } else if (timeType == TimeType.DAY) {
            returnTime = returnTime > 31 ? 1 : returnTime;
        } else if (timeType == TimeType.HOUR) {
            returnTime = returnTime > 23 ? 0 : returnTime;
        } else if (timeType == TimeType.MINUTE
                || timeType == TimeType.SECOND) {
            returnTime = returnTime > 59 ? 0 : returnTime;
        }

    }

    private void verifyTime(int time) {
        if (timeType == TimeType.YEAR
                && (time != ERROR_TIME
                && time < currCalendar.get(Calendar.YEAR))) {
            throw new ScheduleCronException("CRON 年份超出范围:%s, 目标任务:%s",
                    time, analizer.getTask());
        } else if (timeType == TimeType.MONTH
                && (time != ERROR_TIME
                && (time < 1
                || time > 12))) {
            throw new ScheduleCronException("CRON 月份超出范围:%s, 目标任务:%s",
                    time, analizer.getTask());
        } else if (timeType == TimeType.DAY
                && (time != ERROR_TIME
                && (time > currCalendar.getMaximum(Calendar.DAY_OF_MONTH)
                || time < currCalendar.getMinimum(Calendar.DAY_OF_MONTH)))) {
            throw new ScheduleCronException("CRON 日期超出范围:%s, 目标任务:%s",
                    time, analizer.getTask());
        } else if (timeType == TimeType.HOUR
                && (time != ERROR_TIME
                && (time <= 0
                || time > 23))) {
            throw new ScheduleCronException("CRON 小时超出范围:%s, 目标任务:%s",
                    time, analizer.getTask());
        } else if (timeType == TimeType.MINUTE
                && (time != ERROR_TIME
                && (time <= 0
                || time > 59))) {
            throw new ScheduleCronException("CRON 分钟超出范围:%s, 目标任务:%s",
                    time, analizer.getTask());
        } else if (timeType == TimeType.SECOND
                && (time != ERROR_TIME
                && (time <= 0
                || time > 59))) {
            throw new ScheduleCronException("CRON 秒钟超出范围:%s, 目标任务:%s",
                    time, analizer.getTask());
        }
    }

    private int binarySearchTimePoint(Integer[] nums, int min) {
        int index = Arrays.binarySearch(nums, min);
        if (index >= 0) {
            return nums[index];
        } else if (-index <= nums.length) {
            return nums[(-index) - 1];
        } else if (-index == nums.length + 1 || index == -1) {
            return nums[0];
        } else {
            return Integer.MIN_VALUE;
        }
    }

}
